Diusframi Group
url: https://www.diusframi.es/contacto/
email: diusframiinnovacion@gmail.com
VerifactuAPI provides a secure, JSON‑based gateway for submitting invoice records (“Registros de Facturación”, RFs)
to the Agencia Estatal de Administración Tributaria (AEAT), per Real Decreto 1007/2023 and Orden Ministerial HAC/1177/2024.
It abstracts AEAT’s SOAP interfaces into a modern RESTful service that supports both voluntary (VERI*FACTU)
submissions and a non-voluntary (NO-VERI*FACTU) mode for users preferring not to submit under the voluntary regime;i
in this mode, the API securely stores records ensuring integrity, conservation, accessibility, legibility, traceability,
and immutability as mandated by law.
Both M2M (machine-to-machine) and A2M (application-to-machine) clients authenticate via OAuth 2.0. Before any RF operations:
Enroll
Register Obligados Tributarios (OTs)
POST /obligadoTributario to register one or more taxpayer IDs (OTs).Once done, clients may call the main RF endpoints.
--- title: Enrollment & Registration config: layout: elk elk: mergeEdges: false nodePlacementStrategy: NETWORK_SIMPLEX theme: default --- flowchart LR diusframi("Diusframi Group Producer & Distributor") style diusframi fill:#f58404,stroke:#0f1d8d,stroke-width:2px,color:#0f1d8d subgraph Invoicing_Agent["Invoicing Agent Licensed Producer/Distributor"] direction TB m2m("Machine Client Batch Invoicing System") a2m("Application Client Front-end App") end api[["VerifactuApi RFs Processing Service"]] aeat[["AEAT Web-Services External Tax Authority SOAP"]] %% Enrollment flow diusframi --> |"Issues OAuth2 credentials & shared secret"| Invoicing_Agent Invoicing_Agent --> |Enrolls via licensing portal| diusframi %% Registration & processing m2m -->|"OAuth2 POST /obligadoTributario register OTs"| api a2m -->|"OAuth2 POST /obligadoTributario register OTs"| api api -->|SOAP emittance calls| aeat aeat -->|SOAP responses & callbacks| api
| Endpoint | Method | Purpose |
|---|---|---|
POST /obligadoTributario |
POST | Register a new OT (taxpayer) |
PUT /obligadoTributario/{nif} |
PUT | Update OT configuration (mode, email, representative, etc.) |
POST /rfs |
POST | Submit up to 1 000 RFs (Alta or Anulación) |
GET /rfs/estado |
GET | Retrieve status of a single RF (by NIF + refExterna) |
POST /rfs/estado/consulta |
POST | Paginated/status query of multiple RFs |
POST /rfs/consulta |
POST | Bulk fetch stored RFs (non-voluntary mode) |
GET /rfs/{idRegistro} |
GET | Fetch a stored RF (non-voluntary mode) |
POST /res/consulta |
POST | Bulk fetch EventRegisters (REs) |
GET /res/{idRegistro} |
GET | Fetch a single EventRegister (RE) |
POST /aeat/requerimiento |
POST | Submit stored RFs under official AEAT requirement |
POST /aeat/consulta |
POST | Proxy AEAT’s query API via JSON |
GET /integridad/encadenamiento/rfs |
GET | Verify audit-chain integrity for all stored RFs (checks chained-hash continuity) |
GET /integridad/encadenamiento/res |
GET | Verify audit-chain integrity for all stored EventRegisters (REs) |
POST /integridad/registro/rfs |
POST | Check integrity of an array of RFs (validates its hash and XAdES signature) |
POST /integridad/registro/res |
POST | Check integrity of an array of REs (validates its hash and XAdES signature) |
POST /exportar/res |
POST | Export one or more stored RFs in bulk (JSON or ZIP package) |
POST /exportar/res |
POST | Export one or more stored REs in bulk (JSON or ZIP package) |
GET /alertas |
GET | Fetch all active alerts |
POST /alertas/consulta |
POST | Filtered, paginated alert query |
--- config: layout: elk elk: mergeEdges: false nodePlacementStrategy: NETWORK_SIMPLEX theme: default --- flowchart LR %% External Actors subgraph Clients direction TB m2m("Machine Client OAuth2") a2m("App Client OAuth2") end subgraph AEAT_WEBSERVICE[AEAT Webservice endpoints] aeatVol[["AEAT SOAP Service Voluntary Submission"]] aeatNoVol[["AEAT SOAP Service Under Requirement Submission"]] end subgraph verifactuAPI["VerifactuApi"] %% AWS Elements subgraph Databases otDB[("OTs (OEFs)")] rfsBuffer[(RFs Buffer)] rfsStorage[(RFs Storage)] resStorage[(REs Storage)] rfsStatuses[(RFs Statuses)] alertsDB[(Alerts)] end %% Lambdas subgraph Lambdas verifactuLambdas:::VerifactuStyle@{ label: "Voluntary mode Lambdas", shape: div-rect } noVerifactuLambdas:::NonVoluntaryStyle@{ label: "Non-Voluntary mode lambdas", shape: div-rect } commonLambdas:::CommonStyle@{ label: "Common lambdas", shape: div-rect } end %% EventBridge eventBridge@{ label: "Submittion Queue", shape: st-rect } subgraph Endpoints otPath@{ label: "/obligadoTributario", shape: lean-r } rfsPath@{ label: "/rfs", shape: lean-r } resPath@{ label: "/res", shape: lean-r } aeatPath@{ label: "/aeat", shape: lean-r } integridadPath@{ label: "/integridad", shape: lean-r } exportarPath@{ label: "/exportar", shape: lean-r } alertasPath@{ label: "/alertas", shape: lean-r } end end %% Relationships Clients <-.-> Endpoints Endpoints <==> Lambdas verifactuLambdas <--> eventBridge <-.-> aeatVol eventBridge <--> rfsBuffer Lambdas --> alertsDB & otDB verifactuLambdas --> rfsStatuses commonLambdas <-.-> AEAT_WEBSERVICE noVerifactuLambdas <-.-> aeatNoVol noVerifactuLambdas <--> rfsStorage & resStorage & rfsStatuses %% Styles classDef VerifactuStyle color:#009900,stroke:#009900 classDef NonVoluntaryStyle color:#cccc00,stroke:#cccc00 classDef CommonStyle color:#0066cc,stroke:#0066cc
Pre-requisite: Your OT (Obligado Tributario) or OEF (Obligado a Emitir Factura) must be pre-registered via the dedicated onboarding endpoint (not detailed here).
[
{
"factura":{
"idEmisor":"19978910Y",
"numeroSerieFactura":"ABC/DEF/GHI.123_456",
"fechaExpedicionFactura":"01-01-2025"
},
"userReference":"e9dc14b7-e68f-4ae8-847c-3cb8af301c9d",
"nombreRazonEmisor":"Ferretería no.1",
"subsanacion":false,
"rechazoPrevio":"S",
"tipoFactura":"F1",
"tipoRectificativa":"S",
"facturasRectificadas":[
{
"idEmisor":"19978910Y",
"numeroSerieFactura":"ABC/DEF/GHI.123_456",
"fechaExpedicionFactura":"01-01-2025"
}
],
"facturasSustituidas":[
{
"idEmisor":"19978910Y",
"numeroSerieFactura":"ABC/DEF/GHI.123_456",
"fechaExpedicionFactura":"01-01-2025"
}
],
"importeRectificacion":{
"baseRectificada":293140,
"cuotaRectificada":293140,
"cuotaRecargoRectificado":293140
},
"fechaOperacion":"01-01-2025",
"descripcionOperacion":"Venta de artículos varios.",
"facturaSimplificadaArt7273":false,
"facturaSinIdentifDestinatarioArt61d":false,
"macrodato":false,
"emitidaPorTerceroODestinatario":"T",
"tercero":{
"nombreRazon":"Carmen Española Española",
"documento":"19978910Y"
},
"destinatarios":[
{
"nombreRazon":"Carmen Española Española",
"documento":"19978910Y"
}
],
"cupon":false,
"desglose":[
{
"impuesto":"01",
"claveRegimen":"01",
"tipoOperacion":"S1",
"tipoImpositivo":56278,
"baseImponibleOimporteNoSujeto":293140,
"baseImponibleACoste":293140,
"cuotaRepercutida":293140,
"tipoRecargoEquivalencia":56278,
"cuotaRecargoEquivalencia":293140
}
],
"cuotaTotal":293140,
"importeTotal":293140
},
{
"factura": {
"idEmisor": "19978910Y",
"numeroSerieFactura": "ABC/DEF/GHI.123_456",
"fechaExpedicionFactura": "2025-01-01"
},
"userReference": "e9dc14b7-e68f-4ae8-847c-3cb8af301c9d",
"sinRegistroPrevio": false,
"rechazoPrevio": true,
"generadoPor": "T",
"generador": {
"nombreRazon": "Carmen Española Española",
"documento": "19978910Y"
}
}
]
HTTP 202 Request Accepted)Upon receiving your POST, the application immediately performs schema and basic business-rule validations and returns a summary
of which RFs were accepted or rejected. The accepted array contains per‑OT groups where each RF’s qrLink URL is intended
for inclusion on the printed invoice’s QR code. The refExterna value returned must be used for any subsequent status queries
of that RF.
This endpoint also triggers an asynchronous callback (if configured) for richer M2M status updates.
{
"accepted": [
{
"obligadoTributarioId": "19978910Y",
"registros": [
{
"refExterna": "d70e42ab-ce3b-4f2a-bd21-0fefedc7dc54",
"userReference": "e9dc14b7-e68f-4ae8-847c-3cb8af301c9d",
"numSerieFactura": "2NRGGKTU5B",
"qrLink": "https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR?nif=19978910Y&numserie=2NRGGKTU5B&fecha=17-03-2025&importe=131.40"
}
]
}
],
"rejected": [
{
"obligadoTributarioId": "19978910Y",
"cause": "Validation errors",
"registros": [
{
"userReference": "e9dc14b7-e68f-4ae8-847c-3cb8af301c9d",
"numSerieFactura": "2NRGGKTU5B",
"cause": "Invalid date format"
}
]
}
]
}
If you provided X-Callback-URL, VerifactuAPI will POST the final RF statuses as soon as they leave PROCESSING.
Find more information here Webhook Security & HMAC Signature Guide
Headers:
X-Verifactu-Timestamp: UTC timestamp (ISO_8601, e.g. 2025-06-07T15:42:30Z)X-Verifactu-Signature: HMAC‑SHA256 over <data> using your pre‑shared secret.Body: JSON array of status objects:
[
{
"refExterna": "4dc73d25-ae56-4f9f-be3f-d38928cec2cd",
"numeroSerieFactura": "DMLPQ7EJII",
"estado": "AEAT_PARTIALLY_ACCEPTED",
"error": {
"codigo": 4102,
"descripcion": "Codigo[4102].El XML no cumple el esquema. Falta informar campo obligatorio.: DetalleDesglose"
}
}
]
--- config: layout: elk elk: mergeEdges: false nodePlacementStrategy: SIMPLE theme: default --- flowchart TB %% External Elements userData@{ label: "Invoice-data", shape: docs } soapResponse@{ label: "SOAP Response", shape: document } rfsStatusesJson:::CommonStyle@{ label: "RFs statuses payload", shape: docs } %% External Actors subgraph Clients direction TB m2m("Machine Client OAuth2") a2m("App Client OAuth2") end %% VerifactuApi subgraph VerifactuApi postRFs:::PostStyle@{ label: "POST /rfs \n OAuth2 ", shape: lean-r } rfsBuffer[(RFs Buffer)] rfsStatuses[(RFs Statuses)] otDB[("OTs (OEFs)")] alertsDB[(Alerts)] postRFsHandler:::CommonStyle@{ label: "Validation & OT grouping \n Acceptance/Rejection Response ", shape: div-rect } voluntayRFsProcessing:::VerifactuStyle@{ label: "RFs generation (Buffered) \n RF's status records creation ", shape: div-rect } submissionLambda:::VerifactuStyle@{ label: "Submission Lambda \n SOAP response parsing", shape: div-rect } callbackLambda:::CommonStyle@{ label: "Callback RFs Statuses lambda \n (if url provided)", shape: div-rect } eventBridge@{ label: "Submittion Queue", shape: st-rect } end %% SOAP_Coms rfsData:::VerifactuStyle@{ label: "RFs envelope", shape: docs } %% AEAT aeatVol[["AEAT SOAP Service Voluntary Submission"]] %% Relationships m2m & a2m -.-> userData -.-> postRFs postRFs <--> postRFsHandler -->|SQS FIFO </br> serialization| voluntayRFsProcessing postRFsHandler <-->|Read/Write| alertsDB postRFsHandler <-->|Read| otDB voluntayRFsProcessing -->|Write| rfsStatuses voluntayRFsProcessing -->|Write| rfsBuffer voluntayRFsProcessing -->|Create| eventBridge eventBridge -->|"Triggers"| submissionLambda submissionLambda <-->|Read/Update| rfsBuffer submissionLambda -->|Update| rfsBuffer & rfsStatuses & eventBridge submissionLambda <-->|Read/Write| alertsDB submissionLambda <-->|Read/Update| otDB submissionLambda -.- rfsData -.-> aeatVol aeatVol -.-> soapResponse -.-> submissionLambda submissionLambda -->|invokes| callbackLambda callbackLambda -.-> rfsStatusesJson -.-> m2m & a2m %% Styles classDef PostStyle color:#00CC00,fill:#99FF99,stroke:#00CC00 classDef VerifactuStyle color:#009900,stroke:#009900 classDef CommonStyle color:#0066cc,stroke:#0066cc
--- config: layout: elk elk: mergeEdges: false nodePlacementStrategy: SIMPLE theme: default --- flowchart TB %% External Elements userData@{ label: "Invoice-data", shape: docs } %% External Actors subgraph Clients direction TB m2m("Machine Client OAuth2") a2m("App Client OAuth2") end %% VerifactuApi subgraph VerifactuApi postRFs:::PostStyle@{ label: "POST /rfs \n OAuth2 ", shape: lean-r } rfsStatuses[(RFs Statuses)] alertsDB[(Alerts)] otDB[("OTs (OEFs)")] rfsStorage[(RFs Storage)] resStorage[(REs Storage)] postRFsHandler:::CommonStyle@{ label: "Validation & OT grouping \n Acceptance/Rejection Response ", shape: div-rect } noVoluntayRFsProcessing:::NonVoluntaryStyle@{ label: "RF's generation \n (enchaning + signing) \n RF's status records creation", shape: div-rect } end %% Relationships m2m & a2m -.-> userData -.-> postRFs postRFs <--> postRFsHandler --> noVoluntayRFsProcessing postRFsHandler <-->|Read/Write| alertsDB postRFsHandler <-->|Read| otDB noVoluntayRFsProcessing -->|Write| rfsStorage & resStorage & rfsStatuses noVoluntayRFsProcessing -->|Read/Update| otDB %% Styles classDef PostStyle color:#00CC00,fill:#99FF99,stroke:#00CC00 classDef NonVoluntaryStyle color:#cccc00,stroke:#cccc00 classDef CommonStyle color:#0066cc,stroke:#0066cc
Clients can retrieve the current status of any RF submitted to the system. RF statuses are:
Final statuses (COMPLETED, ACCEPTED_BY_AEAT, PARTIALLY_ACCEPTED_BY_AEAT, REJECTED_BY_AEAT, FAILED) are volatile: once a client successfully retrieves a final status, the record is logically deleted (immediately or after a brief delay).
Quickly fetch all RF statuses under the caller’s SIF context.
202 response with details on the status of every RF un der the context of the calling SIF.HTTP 200 Registers Statsuses retrieves successfully){
"registerStatus": {
"refExterna": "4dc73d25-ae56-4f9f-be3f-d38928cec2cd",
"numeroSerieFactura": "DMLPQ7EJII",
"estado": "AEAT_PARTIALLY_ACCEPTED",
"error": {
"codigo": 4102,
"descripcion": "Codigo[4102].El XML no cumple el esquema. Falta informar campo obligatorio.: DetalleDesglose"
}
}
}
Retrieve RF statuses using flexible filters and pagination.
202 response with details on the status of every RF un der the context of the calling SIF.{
"obligadosTributarios": [
"19978910Y"
],
"refExternas": [
"b5cfc7f3-65b4-4ce3-814e-da26eea143ad",
"4dc73d25-ae56-4f9f-be3f-d38928cec2cd"
],
"estados": [
"FAILED",
"SENT"
],
"limit": 1000,
"nextToken": "eyJhbCI6IjEwMCJ9"
}
HTTP 200 Registers Statsuses retrieved successfully){
"rfEstados": [
{
"obligadoTributario": "A39200019",
"rfEstados": [
{
"refExterna": "4dc73d25-ae56-4f9f-be3f-d38928cec2cd",
"numeroSerieFactura": "DMLPQ7EJII",
"estado": "AEAT_PARTIALLY_ACCEPTED",
"error": {
"codigo": 4102,
"descripcion": "Codigo[4102].El XML no cumple el esquema. Falta informar campo obligatorio.: DetalleDesglose"
}
}
]
}
],
"nextToken": "eyJhbCI6IjEwMCJ9"
}
To comply with the VERI*FACTU requirements, our application includes an alerts system that proactively notifies users of any issues affecting invoice register (RF) handling whether during submission to the AEAT web service or in non-voluntary (local-storage) mode. Alerts can be blocking and non-blocking, these are called Warnings in this API and are intendend to bring information about issues affecting RFs processing but that not necesarily interfere with the processing of other Rfs
HTTP 433 Alerts Detected response. The payload lists each alert’s ID, type, and descriptive message, thereby forcing the client to address any underlying issues before proceeding.
In the case of POST/rfs endpoint, for general Alerts the api will react as decribed above, but for thos alerts affecting to a particular OT only, the api will reject the processing of its RFs, stating as cause of rejection the presense of alerts.
in case of warning, thhese will be presented to the client in a different field in the response body at appopiate levelattending if they are general or refered to a particular otQuerying Active Alerts
HTTP 200 Alerts retrieved successfully){
"generales": {
"alertas": [
{
"idAlerta": "ALERT12345",
"tipoAlerta": "Sistema",
"mensaje": "Unresolved discrepancies detected in the submission process.",
"generationTimestamp": "2025-05-23T09:55:31.778Z"
}
],
"avisos": [
{
"idAviso": "ALERT12345",
"mensaje": "RFs submission are temporary on pause due to a malfunction.",
"generationTimestamp": "2025-05-23T09:55:31.778Z"
}
]
},
"porOT": [
{
"obligadoTributario": "19978910Y",
"alertas": [
{
"idAlerta": "ALERT12345",
"tipoAlerta": "Sistema",
"mensaje": "Unresolved discrepancies detected in the submission process.",
"generationTimestamp": "2025-05-23T09:55:31.778Z"
}
],
"avisos": [
{
"idAviso": "ALERT12345",
"mensaje": "RFs submission are temporary on pause due to a malfunction.",
"generationTimestamp": "2025-05-23T09:55:31.778Z"
}
]
}
],
"nextToken": "eyJhbCI6IjEwMCJ9"
}
Query active alerts in your Sistema Informático de Facturación (SIF) context using flexible filters.
200 response with details on the alerts and warnings of every RF un der the context of the calling SIF.{
"generales": true,
"obligadoTributario": [
"A39200019"
],
"tipo": [
"INTEGRITY",
"OTHER"
],
"limit": 1000,
"nextToken": "eyJhbCI6IjEwMCJ9"
}
HTTP 200 Alerts retrieved successfully){
"generales": {
"alertas": [
{
"idAlerta": "ALERT12345",
"tipoAlerta": "Sistema",
"mensaje": "Unresolved discrepancies detected in the submission process.",
"generationTimestamp": "2025-05-23T09:55:31.778Z"
}
],
"avisos": [
{
"idAviso": "ALERT12345",
"mensaje": "RFs submission are temporary on pause due to a malfunction.",
"generationTimestamp": "2025-05-23T09:55:31.778Z"
}
]
},
"porOT": [
{
"obligadoTributario": "19978910Y",
"alertas": [
{
"idAlerta": "ALERT12345",
"tipoAlerta": "Sistema",
"mensaje": "Unresolved discrepancies detected in the submission process.",
"generationTimestamp": "2025-05-23T09:55:31.778Z"
}
],
"avisos": [
{
"idAviso": "ALERT12345",
"mensaje": "RFs submission are temporary on pause due to a malfunction.",
"generationTimestamp": "2025-05-23T09:55:31.778Z"
}
]
}
],
"nextToken": "eyJhbCI6IjEwMCJ9"
}
Creates a new ObligadoTributario (taxpayer) record in the system.
Body: JSON object with all required fields to define the new ObligadoTributario (e.g. NIF, name, email, submission mode, optional representative data).
Updates an existing ObligadoTributario’s details and configuration (e.g. email, submission mode, representative).
Body: JSON object with one or more updatable fields (email, remisionVoluntaria, representante).
Retrieves the current processing status of a single invoice register (RF) by its external reference and issuer NIF.
Bulk-query the processing status of multiple invoice registers (RFs), filtered by taxpayer NIFs, RF identifiers, and/or status values. Supports pagination.
JSON body with any combination of:
Forward a query for invoice registers (RFs) to the AEAT web service and return its response (either data or fault).
JSON body conforming to the ConsultaRequest schema, containing filters such as date ranges, taxpayer NIFs, periods, etc., as defined by AEAT’s consultation API.
Retrieve all currently active alerts across your SIF context, both global alerts and those scoped per taxpayer (OT).
Optional query parameters for pagination:
Retrieve active alerts filtered by one or more criteria within your SIF context.
JSON body (at least one filter required):
Fetch a single invoice register (RF) by its unique idRegistro when operating in non-voluntary mode.
Retrieve the full details of one or more stored invoice registers (RFs) when operating in non-voluntary mode.
Fetch a single event register (RE) by its unique idRegistro when operating in non-voluntary mode.
Retrieve one or more event registers (REs) matching supplied filters when operating in non-voluntary mode.
Non-voluntary submission of stored invoice registers (RFs) to the AEAT web service under official requirement.